﻿using System;
using System.Diagnostics.Contracts;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tradex.ComponentModel {
    
    /// <summary>
    /// The Dispatcher class is a central component that can distribute objects of a specific type to
    /// different targets. This is usefull if an incoming stream of classes neets to be distributed to
    /// different handling methods based on 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Dispatcher<T> {

        Dictionary<Type, Action<T>> _Targets = new Dictionary<Type, Action<T>>();
        Action<T> _DefaultTarget;

        /// <summary>
        /// Registers an action to execute for a parameter of a specific type. The type has
        /// to be an subtype of the original parameter T. The dispatched method will get the
        /// appropriately casted type.
        /// </summary>
        /// <typeparam name="K"></typeparam>
        /// <param name="action"></param>
        public void Register<K>(Action<K> action) where K : T {
            if (action != null) {
                _Targets[typeof(K)] = (item => action((K)item));
            } else {
                _Targets[typeof(K)] = null;
            }
        }

        /// <summary>
        /// Regsiters the action to call if no specific action is registered for a parameter.
        /// This can serve as generic fallback method, for example to forward items with no
        /// registered handler to another dispatcher.
        /// </summary>
        /// <param name="action"></param>
        public void RegisterDefault(Action<T> action) {
            _DefaultTarget = action;
        }

        /// <summary>
        /// Dispatches an item to a registered handling method.
        /// </summary>
        /// <param name="item"></param>
        public void Dispatch(T item) {
            if (item == null) {
                throw new ArgumentException("value can not be null", "item");
            }
            Contract.EndContractBlock();

            Action<T> action;
            if (!_Targets.TryGetValue (item.GetType(), out action)) {
                if (_DefaultTarget == null) {
                    throw new ArgumentException("no target registered", "item");
                } else {
                    _DefaultTarget(item);
                    return;
                }
            }
            action(item);
        }
    }

}
